package com.sanri.tools.modules.core.service.plugin;

import com.dtflys.forest.Forest;
import com.sanri.tools.modules.core.service.CachedData;
import com.sanri.tools.modules.core.service.file.FileManager;
import com.sanri.tools.modules.core.service.plugin.dtos.InstallPluginInfo;
import com.sanri.tools.modules.core.service.plugin.dtos.MarketPlugin;
import com.sanri.tools.modules.core.utils.Version;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.similarity.JaccardSimilarity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

@Component
@Slf4j
public class MarketPluginCacheData extends CachedData<List<MarketPlugin>> {
    @Override
    protected void refreshCache() throws Exception {
        List<MarketPlugin> marketPlugins = marketPlacePlugins();
        this.data = marketPlugins;
    }

    @Autowired
    private PluginRepoManager pluginRepoManager;

    @Autowired
    private FileManager fileManager;

    @Autowired
    private ModuleClassLoaderRepo moduleClassLoaderRepo;

    /**
     * 卸载插件
     * @param pluginId
     * @throws IOException
     */
    public void uninstall(String pluginId) throws IOException {
        moduleClassLoaderRepo.unLoadModule(pluginId);
    }

    /**
     * 安装插件
     * @param marketPlugin
     */
    public void install(MarketPlugin marketPlugin) throws IOException {
        InstallPluginInfo installPluginInfo = new InstallPluginInfo(marketPlugin);
        installPluginInfo.setDependencyPath(new InstallPluginInfo.DependencyPath(marketPlugin.getId(), new Version(marketPlugin.getVersion())));
        List<InstallPluginInfo> downloads = findDownloads(marketPlugin, installPluginInfo);

        // 如果有同名插件, 则选路径短的
        Map<String, InstallPluginInfo> filterDownload = new LinkedHashMap<>();
        for (InstallPluginInfo download : downloads) {
            if (filterDownload.containsKey(download.getPluginId())){
                InstallPluginInfo exist = filterDownload.get(download.getPluginId());
                if (download.getDependencyPath().length() < exist.getDependencyPath().length()){
                    // 如果当前路径更短, 使用更短路径;否则维持原样
                    filterDownload.put(download.getPluginId(),download);
                }
            }else {
                filterDownload.put(download.getPluginId(), download);
            }
        }

        // 下载插件
        File downloadDir = fileManager.mkTmpDir("plugin/download/" + System.currentTimeMillis());

        long startTime = System.currentTimeMillis();
        log.info("开始下载插件[{}]及其所有依赖项 {} 个", marketPlugin.getName(), filterDownload.size() - 1);
        for (InstallPluginInfo download : filterDownload.values()) {
            final String downloadAddress = download.getDownloadAddress();
            final URL url = new URL(downloadAddress);
            try(final InputStream inputStream = url.openStream();
                final FileOutputStream fileOutputStream = new FileOutputStream(new File(downloadDir, download.getFilename()))){
                IOUtils.copy(inputStream, fileOutputStream);
            }
        }
        log.info("插件[{}]下载结束, 耗时:{} ms, 准备安装",marketPlugin.getName(), (System.currentTimeMillis() - startTime));

        // 安装插件
        List<InstallPluginInfo> realInstalls = new ArrayList<>(filterDownload.values());
        Collections.reverse(realInstalls);
        for (InstallPluginInfo realInstall : realInstalls) {
            log.info("安装插件: {}:{}",realInstall.getPluginId(),realInstall.getVersion());
            File jarFile = new File(downloadDir, realInstall.getFilename());
            moduleClassLoaderRepo.loadModule(realInstall, jarFile);
        }
    }


    /**
     * 查找插件要下载的所有数据
     * @param marketPlugin
     * @return
     */
    public List<InstallPluginInfo> findDownloads(MarketPlugin marketPlugin, InstallPluginInfo parent) {
        List<InstallPluginInfo> downloads = new ArrayList<>();
        downloads.add(parent);

        List<MarketPlugin> dependencies = marketPlugin.getDependencies();
        if (CollectionUtils.isNotEmpty(dependencies)){
            for (MarketPlugin dependency : dependencies) {
                InstallPluginInfo installPluginInfo = new InstallPluginInfo(dependency);
                InstallPluginInfo.DependencyPath dependencyPath = new InstallPluginInfo.DependencyPath(marketPlugin.getId(), new Version(marketPlugin.getVersion()));
                dependencyPath.setPrev(parent.getDependencyPath());
                installPluginInfo.setDependencyPath(dependencyPath);
                List<InstallPluginInfo> partDownloads = findDownloads(dependency, installPluginInfo);
                downloads.addAll(partDownloads);
            }
        }
        return downloads;
    }


    /**
     * 关键字搜索, 匹配程度 >70% 的当选
     * @param keyword
     * @return
     */
    public List<MarketPlugin> search(String keyword){
        if (StringUtils.isBlank(keyword)){
            for (MarketPlugin datum : data) {
                final boolean moduleIsLoad = moduleClassLoaderRepo.moduleIsLoad(datum.getId());
                datum.setLoaded(moduleIsLoad);
            }
            return data;
        }

        JaccardSimilarity jaccardSimilarity = new JaccardSimilarity();
        List<MarketPlugin> chosePlugins = new ArrayList<>();
        for (MarketPlugin datum : data) {
            Double authorScore = jaccardSimilarity.apply(datum.getAuthor(), keyword);
            Double nameScore = jaccardSimilarity.apply(datum.getName(), keyword);
            Double descScore = jaccardSimilarity.apply(datum.getDesc(), keyword);
            Double score = nameScore * 120 + authorScore * 110  + descScore * 100;

            if (score >= 70){
                final boolean moduleIsLoad = moduleClassLoaderRepo.moduleIsLoad(datum.getId());
                datum.setLoaded(moduleIsLoad);
                chosePlugins.add(datum);
            }
        }
        return chosePlugins;
    }

    /**
     * 可以安装的插件列表
     */
    public List<MarketPlugin> marketPlacePlugins() throws IOException {
        List<MarketPlugin> marketPlugins = new ArrayList<>();
        final Iterator<String> iterator = pluginRepoManager.list().iterator();
        while (iterator.hasNext()){
            final String repo = iterator.next();
            if (StringUtils.isNotBlank(repo)){
                try{
                    final URL url = new URL(repo);
                    try(final InputStream inputStream = url.openStream()){
                        final String pluginString = IOUtils.toString(inputStream);
                        final String[] pluginArray = StringUtils.split(pluginString, '\n');
                        for (String pluginInfo : pluginArray) {
                            final String[] columns = StringUtils.splitByWholeSeparator(pluginInfo, "::");
                            if (columns.length != 6){
                                log.warn("仓库[{}]文件格式错误", repo);
                                break;
                            }
                            final MarketPlugin marketPlugin = new MarketPlugin(columns[0], columns[1], columns[2], columns[3], columns[4], columns[5]);
                            marketPlugins.add(marketPlugin);
                        }
                    }
                }catch (MalformedURLException e){
                    log.error("仓库[{}]格式不正确, 已经删除", repo);
                    pluginRepoManager.remove(repo);
                }catch (IOException e){
                    log.error("仓库[{}] 连接失败, 网络异常", repo);
                }
            }
        }

        return marketPlugins;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 一天刷新一次
        this.refreshInterval = 1 * 24 * 60 * 60 * 1000;
        super.afterPropertiesSet();
    }
}
